/**
 * The Whole Script is Written and Copyright by Philip Beschorner
 * Basic Idea to use an Array: http://learninggeekblog.wordpress.com/2013/10/30/dungeon-grind-procedural-dungeon-generation-tutorial/ and
 * http://gamedevelopment.tutsplus.com/tutorials/create-a-procedurally-generated-dungeon-cave-system--gamedev-10099
 *
 * But I kinda went my own way.
 *
 * The script basically creates a random Level Layout in an Array and then chooses corresponding prefabs for each element and spwans them in the scene.
 * Each Element has 1-4 Exits, which makes a minimum of 5 different Elements

Version History:

V0.1: Erste Schritte der prozedualen Generierung

V0.2: Vorbereitung auf eine Abfrage, die es nur erlaubt, dass elemente in das Array gefügt werden wenn sie kein anderes um sich herum haben

V0.3: Das Skript erkennt nun wie viele Ausgänge ein Levelelement hat und welche, es speichert entsprechende Werte als Struct in einer Liste.
Hierzu weitere Methoden zum erkennen der Ausgänge.

V0.4: Skript sollte nun die Entsprechenden LevelElemente wählen und erstellen, wirft momentan eine Null pointer exception weil er die Prefabs nicht findet.

V1.0: Funktionierendes Skript!
Erstellt Map aus vorgefertigten Prefabs, diese müssen in einem Ordner
"resources" liegen, es gibt momentan 5 vordefinierte, simple Prefabs hierfür.
Das Skript rotiert die Level Elemente automatisch, hierfür müssen die Ausgänge
genordet sein, ein Element mit 2 Ausgägnen gewinkelt hat seine Ausgänge im
Norden und Osten!
Spätere Erweiterung soll erfolgen, dass das Skript aus einer Liste ein
zufälliges Prefab auswählt.
System zum Spawnen der Levelelemente grob angefangen (noch auskommentiert)

V1.1: Spieler wird an zufälliger Position der Welt gespawnt, verbesserung des
Zufallsalgrithmus: Die Levelelemente werden in zufälliger Richtung mit einander
verbunden.
V1.1.1 Bugfixing, Anpassung an finale Szene

 *
 **/

using UnityEngine;
using System.Collections;
using System.Collections.Generic;



public class ProcedualGenerator : MonoBehaviour {
	

	
	public int tileSize; //change this to determine the size of the game world (e.g. 10 = 10*10 Array)
	public int maxTileNum; //change this to determine how many tiles should be placed in the array initially
	public int[,] tileMap; //The array where the tiles -> world layout is initially saved
	//public GameObject fence;
	public int lvlElSize;
	//int maxChunkSize = 3;
	int currentChunkSize; //not used -> may be used to add chunks of tiles into the array rather then single elements, FUNCION NOT EXISTANT
	
	
	Dictionary<Vector2,Vector2> combinedV;  //this dictionary saves which tiles have already been combined
	
	
	public struct LevelElements //the struct stores important properties of each element
	{
		public Vector3 elPos; //position where the levelelement will be placed in the scene
		public int exitCount; //number of exits of the levelelement
		public int elIndex; //index of the element (no further use at the moment)
		public bool exitNorth;
		public bool exitSouth;
		public bool exitEast;
		public bool exitWest;
	}
	
	
	public List<LevelElements> levelElList; //list holds the LevelElements structs
	
	
	
	// Use this for initialization
	void Start () 
	{
		tileSize = 7;
		maxTileNum = 6;
		lvlElSize = 50;
		
		
		
		
		tileMap = new int[tileSize,tileSize]; //Create the Array
		firstMapPass(); //Adds randomized tiles in the array
		combinedV = new Dictionary<Vector2,Vector2>(); //Create the Dictionary
		secondMapPass(); //connects the single tiles
		levelElList = new List<LevelElements>();
		thirdMapPass(); //Saves properties for all elements as Structs in a list
		elementSpawner(); //Spawns the Level Elements, creating a Map
		spawnPlayer();

		
		/*int count = 0;
		for (int j = 0; j < tileSize;j++) 
		{
			for (int k = 0; k < tileSize;k++) 
			{
				if(tileMap[j,k] == 1)
				{
				GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
				cube.transform.position = new Vector3(j, 0, k);
					cube.renderer.material.color = Color.red;
				}
				else if(tileMap[j,k] == 2)
				{
						GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
						cube.transform.position = new Vector3(j, 0, k);
				}
					count++;
				}
			}

		print(count + " blöcke erstellt");*/
		
		
		/*string nmb = "";
		for (int j = 0; j < tileSize;j++) 
		{
			
			for (int k = 0; k < tileSize;k++) 
			{
				nmb += tileMap[j,k];
				
			}
			print(nmb);
			nmb = "";
		}*/
		
	}
	
	
	
	
	
	
	// Update is called once per frame
	// NOT USED FOR THE WORLD CREATION
	void Update () {
		
	}
	
	
	
	
	
	/* This function fills an 2- Dimensional integer Array with random "markers".
	 * Each of these markers represent a single room for the game.
	 * Each position in the array with a room gets the number 1 while each position without a room has the number 0.
	 * 
	 * The function uses random row and colum numbers to define in which position of the Array a level marker will be placed.
	 * */
	void firstMapPass ()
	{
		int currentTileNum = 0; //for counting up to the maximum tile numbers
		
		while (currentTileNum < maxTileNum){
			
			int rX = (int)Random.Range(0,tileSize);
			int rY = (int)Random.Range(0,tileSize);
			
			
			if(tileMap[rX,rY] == 0 && borderCheck(rX,rY) == true) //checks if an element is saved at the actual chosen place and if there is one next to it, only saves when nothing is nearby
			{
				tileMap[rX,rY] = 1;
				currentTileNum ++;
			}
		}
		/*string nmb = "";
		for (int j = 0; j < tileSize;j++) 
		{
			
			
			
			
			for (int k = 0; k < tileSize;k++) 
			{
				nmb += tileMap[j,k];
				
			}
			print(nmb);
			nmb = "";

		}
		*/
		
	}
	
	/*
	 * Checks if there is an element next to the one the alorithm wants to add to the array.
	 * Also prevents stack overflow by only checking if the coordinates of the element don't match the boundaries of the array
	 */
	bool borderCheck(int rX,int rY)
	{
		
		if(rX != 0)
		{
			if(tileMap[rX-1,rY] == 1)
				return false;
		}
		
		if(rX != tileSize-1)
		{
			if(tileMap[rX +1,rY] == 1)
				return false;
		}
		if(rY != 0)
		{
			if(tileMap[rX,rY-1] == 1)
				return false;
		}
		
		if(rY != tileSize-1)
		{
			if(tileMap[rX,rY+1] == 1)
				return false;
		}
		
		
		return true;
	}
	
	
	/*
	 * This function uses the function findclosest(vector2) for each tile and then attempts to connect the two tiles via the shortest way.
	 * 
	 */
	void secondMapPass (){
		int cnt = 0;
		for (int row = 0; row < tileSize;row++) {
			
			for (int col = 0; col < tileSize;col++) {
				
				if(tileMap[row,col] == 1){
					
					Vector2 firstEl = new Vector2(row,col);
					Vector2 closestEl = findclosest(firstEl);
					//print (firstEl+" "+closestEl);
					//print (closestEl);
					cnt++;
					if(cnt == tileSize && closestEl == new Vector2(0,0))
					{
						closestEl = firstEl;
					}
					connectClosest(firstEl,closestEl);
					
					
				}
				
			}
			
			
		}
	}
	
	/*
	 * This function finds the closest Element to a given Element, represented as a Vector2 where the coordinates match the position in the Array it uses (where the randomized tiles are placed)
	 * It uses a vector 3 to save the actual closest found element and uses the 3rd vector to save the actual closest distance to find the closest element
	 * returns the closest element as a Vector2
	 */
	
	Vector2 findclosest (Vector2 firstEl) {
		
		Vector3 min = new Vector3(0,0,-1); //third vector is -1 when initialized to prevent bugs when attempting to save the first closest element
		float distance = 0;
		
		
		for (int row = 0; row < tileSize;row++) {
			
			for (int col = 0; col < tileSize;col++) {
				
				
				if(tileMap[row,col] == 1 && (new Vector2(row,col) != firstEl) && combVCheck(new Vector2(row,col), firstEl) == true) 	
				{
					distance = Vector2.Distance(firstEl,new Vector2(row,col));
					//print(distance);
					
					//if(tileMap[row,col] == 1 && combVCheck(new Vector2(row,col),firstEl)) distance = Vector2.Distance(firstEl,new Vector2(row,col)); 
					//checks when a tile is saved in the array, saves the distance to the first element, also checks if the elements have been connected already
					if(distance < min.z || min.z < 0 ) 				
					{
						min = new Vector3(row,col,distance);
					}
					
				}
				
				
			}
			
			
		}
		
		
		
		//checks if the distance to the element is closer then the distance of the actual closest, also checks if it's the first checked element
		
		
		combinedV.Add(firstEl,min);
		return min;
		
	}
	
	/*
	 * Checks if the two tiles in the array have already been combined, returns false if that's the case
	 * causing the findclosest() function not to save that point as the closest and to search on
	 */
	/*
	bool combVCheck(Vector2 combV,Vector2 firstEl)
	{
		Vector2 frstel = new Vector2();
		if(combinedV.ContainsKey(combV))
		{
			combinedV.TryGetValue(combV, out frstel);
				if(frstel == firstEl)
				{
					return false;
				}
		}
		return true;
	}*/
	
	bool combVCheck(Vector2 combV, Vector2 firstEl)
	{
		//Vector2 frstel = new Vector2();
		if(combinedV.ContainsKey(combV))
		{
			return false;
		}
		return true;
	}
	
	
	void connectClosest(Vector2 firstEl,Vector2 closestEl)
	{
		int rnd = Random.Range(0,2);
		
		Vector2 vNow = firstEl;
		Vector2 vEnd = closestEl;
		Vector2 vThis;
		
		if(rnd == 0)
		{
			vThis = connectX(vNow,vEnd);
			connectY(vThis,vEnd);
		}
		
		else
		{
			vThis = connectY(vNow,vEnd);
			connectX(vThis,vEnd);
			
		}


		
		/*if(vNow.x != vEnd.x) //&& closestEl != new Vector2(0,0)
		{
			if(vNow.x < vEnd.x)
			{
				while(vNow.x < vEnd.x)
				{
					vNow.x ++;
					if(tileMap[(int)vNow.x,(int)vNow.y] == 0)
					{
						tileMap[(int)vNow.x,(int)vNow.y] = 2;
					}
					
				}
			}
			
			else if(vNow.x > vEnd.x)
			{
				while(vNow.x > vEnd.x)
				{
					vNow.x --;
					if(tileMap[(int)vNow.x,(int)vNow.y] == 0)
					{
						tileMap[(int)vNow.x,(int)vNow.y] = 2;
					}
					
				}
			}
		}
		
		if(vNow.y != vEnd.y)
		{
			if(vNow.y < vEnd.y)
			{
				while(vNow.y < vEnd.y)
				{
					vNow.y ++;
					if(tileMap[(int)vNow.x,(int)vNow.y] == 0)
					{
						tileMap[(int)vNow.x,(int)vNow.y] = 2;
					}
					
				}
			}
			
			
			else if(vNow.y > vEnd.y)
			{
				while(vNow.y > vEnd.y)
				{
					vNow.y --;
					if(tileMap[(int)vNow.x,(int)vNow.y] == 0)
					{
						tileMap[(int)vNow.x,(int)vNow.y] = 2;
					}
					
				}
			}
			
		}*/
		
	}

	Vector2 connectX(Vector2 firstEl,Vector2 closestEl)
	{
		Vector2 vNow = firstEl;
		Vector2 vEnd = closestEl;
		
		
		if(vNow.x != vEnd.x) //&& closestEl != new Vector2(0,0)
		{
			if(vNow.x < vEnd.x)
			{
				while(vNow.x < vEnd.x)
				{
					vNow.x ++;
					if(tileMap[(int)vNow.x,(int)vNow.y] == 0)
					{
						tileMap[(int)vNow.x,(int)vNow.y] = 2;
					}
					
				}
			}
			
			else if(vNow.x > vEnd.x)
			{
				while(vNow.x > vEnd.x)
				{
					vNow.x --;
					if(tileMap[(int)vNow.x,(int)vNow.y] == 0)
					{
						tileMap[(int)vNow.x,(int)vNow.y] = 2;
					}
					
				}
			}
		}
		
		return vNow;
	}
	
	
	Vector2 connectY(Vector2 firstEl,Vector2 closestEl)
	{
		Vector2 vNow = firstEl;
		Vector2 vEnd = closestEl;
		
		
		if(vNow.y != vEnd.y)
		{
			if(vNow.y < vEnd.y)
			{
				while(vNow.y < vEnd.y)
				{
					vNow.y ++;
					if(tileMap[(int)vNow.x,(int)vNow.y] == 0)
					{
						tileMap[(int)vNow.x,(int)vNow.y] = 2;
					}
					
				}
			}
			
			
			else if(vNow.y > vEnd.y)
			{
				while(vNow.y > vEnd.y)
				{
					vNow.y --;
					if(tileMap[(int)vNow.x,(int)vNow.y] == 0)
					{
						tileMap[(int)vNow.x,(int)vNow.y] = 2;
					}
					
				}
			}
			
		}
		
		return vNow;
		
	}


		
		
		

	/*
	 * Goes through the array and checks each levelmarker for it's properties, like the number of exits etc. I
	 * It saves those properties as a struct (LevelElements) in a List (levelElList)
	  */
	
	public void thirdMapPass()
	{
		int eIndex = 0;
		
		for (int row = 0; row < tileSize;row++) 
		{
			
			for (int col = 0; col < tileSize;col++) 
			{
				if(tileMap[row,col] == 1 || tileMap[row,col] == 2)
				{
					int eCount = 0;
					Vector3 ePos = new Vector3(row*lvlElSize,0,col*lvlElSize);
					int elIndex = eIndex;
					eIndex ++;
					bool exitNorth = northCheck(row,col);
					bool exitSouth = southCheck(row,col);
					bool exitEast = eastCheck(row,col);
					bool exitWest = westCheck(row,col);
					if(northCheck(row,col) == true)
					{
						eCount++;
					}
					if(southCheck(row,col) == true)
					{
						eCount++;
					}
					if(eastCheck(row,col) == true)
					{
						eCount++;
					}
					if(westCheck(row,col) == true)
					{
						eCount++;
					}
					int exitCount = eCount;
					
					LevelElements lE = new LevelElements();
					lE.elPos = ePos;
					lE.elIndex = elIndex;
					lE.exitNorth = exitNorth;
					lE.exitSouth = exitSouth;
					lE.exitWest = exitWest;
					lE.exitEast = exitEast;
					lE.exitCount = exitCount;
					
					levelElList.Add(lE);
					//print (lE.elIndex + "" + lE.elPos + "" + lE.exitCount + "" + lE.exitEast + "");
					
				}
				
			}
			
		}
		
		
	}
	/*
	 * Some small functions who check what exits each Level Element has
	 */
	bool northCheck(int rX,int rY)
	{
		
		if(rX != 0)
		{
			if(tileMap[rX-1,rY] != 0)
				return true;
		}
		return false;
	}
	
	bool southCheck(int rX,int rY)
	{
		if(rX != tileSize-1)
		{
			if(tileMap[rX +1,rY] != 0)
				return true;
		}
		return false;
	}
	
	bool westCheck(int rX,int rY)
	{
		if(rY != 0)
		{
			if(tileMap[rX,rY-1] != 0)
				return true;
		}
		return false;
	}
	
	bool eastCheck(int rX,int rY)
	{
		if(rY != tileSize-1)
		{
			if(tileMap[rX,rY+1] != 0)
				return true;
		}
		return false;
	}
	
	/*
	 * Used for Spawning the Level Elements
	 * Instanciates each object to spawn it at it's corresponding position
	 */
	void elementSpawner()
	{
		
		string elType;
		int elRotation;
		
		foreach (LevelElements currentLvlEl in levelElList)
		{
			elType = elementPicker(currentLvlEl);
			//print(elType);
			elRotation = elementRotator(currentLvlEl);
			//print (elRotation);
			
			//GameObject go = (GameObject)Resources.Load(elType);
			GameObject myEl = (GameObject)Instantiate(Resources.Load(elType),new Vector3(currentLvlEl.elPos.x,0,currentLvlEl.elPos.z),Quaternion.Euler(0,(float)elRotation,0)); //PROBLEMZONE!!!
			//myEl.renderer.material.color = Color.red;
			
		}
	}
	
	
	/*
	 * 
	 * 
	 */
	string elementPicker(LevelElements currentLvlEl)
	{
		int elExits = currentLvlEl.exitCount;
		
		switch(elExits)
		{
		case 1:
		{
			return("one_Exit");
		}
		case 2:
		{
			if((currentLvlEl.exitEast == true && currentLvlEl.exitWest == true) || (currentLvlEl.exitNorth == true && currentLvlEl.exitSouth == true))
			{
				return("two_Exits_Straight");
			}
			else
				return("two_Exits_Angled");
		}
		case 3:
		{
			return("three_Exits");
		}
		case 4:
		{
			return("four_Exits");
		}
		default:
			return(null);
			
		}
	}
	
	
	int elementRotator(LevelElements currentLvlEl)
	{
		int elExits = currentLvlEl.exitCount;
		
		switch(elExits)
		{
		case 1:
		{
			if(currentLvlEl.exitEast == true)
			{
				return(90);
			}
			else if(currentLvlEl.exitWest == true)
			{
				return(-90);
			}
			else if(currentLvlEl.exitSouth == true)
			{
				return(180);
			}
			else
			{
				return(0);
			}
		}
		case 2:
		{
			if((currentLvlEl.exitEast == true && currentLvlEl.exitWest == true) || (currentLvlEl.exitEast == true && currentLvlEl.exitSouth == true))
			{
				return(90);
			}
			else if(currentLvlEl.exitSouth == true && currentLvlEl.exitWest == true)
			{
				return(180);
			}
			else if(currentLvlEl.exitNorth == true && currentLvlEl.exitWest == true)
			{
				return(-90);
			}
			else
			{
				return(0);
			}
		}
		case 3:
		{
			if(currentLvlEl.exitEast != true)
			{
				return(-90);
			}
			else if(currentLvlEl.exitWest != true)
			{
				return(90);
			}
			else if(currentLvlEl.exitNorth != true)
			{
				return(180);
			}
			else
			{
				return(0);
			}
		}
		case 4:
		{
			return(0);
		}
		default:
			return(0);
		}
		
	}
	

	Vector3 playerSpawn()
	{

		Vector3 theSpawn = new Vector3(0,1,0);
		Vector3[] possibleSpawns = new Vector3[maxTileNum];
		int index = 0;

		foreach (LevelElements currentLvlEl in levelElList)
		{

			if(theSpawn == new Vector3(0,1,0))
			{
				theSpawn = currentLvlEl.elPos;
			}
			else if(currentLvlEl.exitCount == 1)
			{
				possibleSpawns[index] = currentLvlEl.elPos;
				index++;
			}			
		}
		int chosenSpawn = Random.Range(0,index);

		if(index > 0)
		{
			theSpawn = possibleSpawns[chosenSpawn];
			//print(theSpawn);
		}
		theSpawn.y = (float)0.1f;
		return theSpawn;
	}


	void spawnPlayer()
	{
		GameObject myPlayer = GameObject.Find("Player");
		myPlayer.transform.position = playerSpawn();
	}








}








/*

				
				//currentChunkSize = Random.Range(0,maxChunkSize);
				


			}

		
		}*/



